package org.jim.server.http.mvc;

import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jim.server.http.annotation.RequestPath;
import org.tio.utils.json.Json;

import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.Paranamer;

import cn.hutool.core.util.ArrayUtil;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.ClassAnnotationMatchProcessor;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.MethodAnnotationMatchProcessor;

/**
 * @author WChao
 * 2017年7月1日 上午9:05:30
 */
public class Routes {
    private static Logger log = LoggerFactory.getLogger(Routes.class);
//	private HttpServerConfig httpServerConfig = null;

//	private String[] scanPackages = null;

    /**
     * 路径和对象映射
     * key: /user
     * value: object
     */
    public Map<String, Object> pathBeanMap = new HashMap<>();

    /**
     * 路径和class映射
     * 只是用来打印的
     * key: /user
     * value: Class
     */
    public Map<String, Class<?>> pathClassMap = new HashMap<>();

    /**
     * 路径和class映射
     * key: class
     * value: /user
     */
    public Map<Class<?>, String> classPathMap = new HashMap<>();

    /**
     * Method路径映射
     * key: /user/update
     * value: method
     */
    public Map<String, Method> pathMethodMap = new HashMap<>();
    /**
     * Method路径映射
     * 只是用于打印日志
     * key: /user/update
     * value: method string
     */
    public Map<String, String> pathMethodstrMap = new HashMap<>();

    /**
     * 方法参数名映射
     * key: method
     * value: ["id", "name", "scanPackages"]
     */
    public Map<Method, String[]> methodParamnameMap = new HashMap<>();

    /**
     * 方法和对象映射
     * key: method
     * value: bean
     */
    public Map<Method, Object> methodBeanMap = new HashMap<>();

    /**
     * @author: WChao
     */
    public Routes(String[] scanPackages) {
//		this.scanPackages = scanPackages;

        if (scanPackages != null) {
            final FastClasspathScanner fastClasspathScanner = new FastClasspathScanner(scanPackages);
//			fastClasspathScanner.verbose();
            fastClasspathScanner.matchClassesWithAnnotation(RequestPath.class, new ClassAnnotationMatchProcessor() {
                @Override
                public void processMatch(Class<?> classWithAnnotation) {
                    try {
                        Object bean = classWithAnnotation.newInstance();
                        RequestPath mapping = classWithAnnotation.getAnnotation(RequestPath.class);
                        String beanPath = mapping.value();
//						if (!StringUtils.endsWith(beanUrl, "/")) {
//							beanUrl = beanUrl + "/";
//						}

                        beanPath = formateBeanPath(beanPath);

                        Object obj = pathBeanMap.get(beanPath);
                        if (obj != null) {
                            log.error("mapping[{}] already exists in class [{}]", beanPath, obj.getClass().getName());
                        } else {
                            pathBeanMap.put(beanPath, bean);
                            pathClassMap.put(beanPath, classWithAnnotation);
                            classPathMap.put(classWithAnnotation, beanPath);
                        }
                    } catch (Exception e) {

                        log.error(e.toString(), e);
                    }
                }
            });

            fastClasspathScanner.matchClassesWithMethodAnnotation(RequestPath.class, new MethodAnnotationMatchProcessor() {
                @Override
                public void processMatch(Class<?> matchingClass, Executable matchingMethodOrConstructor) {
//					log.error(matchingMethodOrConstructor + "");
                    RequestPath mapping = matchingMethodOrConstructor.getAnnotation(RequestPath.class);

                    String methodName = matchingMethodOrConstructor.getName();


                    String methodPath = mapping.value();
                    methodPath = formateMethodPath(methodPath);
                    String beanPath = classPathMap.get(matchingClass);

                    if (StringUtils.isBlank(beanPath)) {
                        log.error("方法有注解，但类没注解, method:{}, class:{}", methodName, matchingClass);
                        return;
                    }

                    Object bean = pathBeanMap.get(beanPath);
                    String completeMethodPath = methodPath;
                    if (beanPath != null) {
                        completeMethodPath = beanPath + methodPath;
                    }

                    Class<?>[] parameterTypes = matchingMethodOrConstructor.getParameterTypes();
                    Method method;
                    try {
                        method = matchingClass.getMethod(methodName, parameterTypes);

                        Paranamer paranamer = new BytecodeReadingParanamer();
                        String[] parameterNames = paranamer.lookupParameterNames(method, false); // will return null if not found

                        Method checkMethod = pathMethodMap.get(completeMethodPath);
                        if (checkMethod != null) {
                            log.error("mapping[{}] already exists in method [{}]", completeMethodPath, checkMethod.getDeclaringClass() + "#" + checkMethod.getName());
                            return;
                        }

                        pathMethodMap.put(completeMethodPath, method);
                        pathMethodstrMap.put(completeMethodPath, matchingClass.getName() + "." + method.getName() + "(" + ArrayUtil.join(parameterNames, ",") + ")");
                        methodParamnameMap.put(method, parameterNames);
                        methodBeanMap.put(method, bean);
                    } catch (Exception e) {
                        log.error(e.toString(), e);
                    }
                }
            });

            fastClasspathScanner.scan();

            log.info("class  mapping\r\n{}", Json.toFormatedJson(pathClassMap));
//			log.info("classPathMap scan result :\r\n {}\r\n", Json.toFormatedJson(classPathMap));
            log.info("method mapping\r\n{}", Json.toFormatedJson(pathMethodstrMap));
//			log.info("methodParamnameMap scan result :\r\n {}\r\n", Json.toFormatedJson(methodParamnameMap));
        }
    }

    /**
     * 格式化成"/user","/"这样的路径
     *
     * @param initPath
     * @return
     * @author: WChao
     */
    private static String formateBeanPath(String initPath) {
        if (StringUtils.isBlank(initPath)) {
            return "/";
        }
        initPath = StringUtils.replaceAll(initPath, "//", "/");
        if (!StringUtils.startsWith(initPath, "/")) {
            initPath = "/" + initPath;
        }

        if (StringUtils.endsWith(initPath, "/")) {
            initPath = initPath.substring(0, initPath.length() - 1);
        }
        return initPath;
    }

    private static String formateMethodPath(String initPath) {
        if (StringUtils.isBlank(initPath)) {
            return "/";
        }
        initPath = StringUtils.replaceAll(initPath, "//", "/");
        if (!StringUtils.startsWith(initPath, "/")) {
            initPath = "/" + initPath;
        }

        return initPath;
    }

    /**
     * @param args
     * @author: WChao
     */
    public static void main(String[] args) {

    }
}
